﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using BulldogCMS.Core.Storage.Providers;
using BulldogCMS.Core.Storage.DataTypes;
using BulldogCMS.Core.Templates;
using System.Diagnostics;

namespace BulldogCMS.Core.Storage
{
    [DebuggerDisplay("{Path}")]
    public class Node
    {
        private NodeBag _bag = null;

        internal List<NodeVersionMeta> VersionsMeta = null;

        #region constructors

        internal Node()
        {

        }

        public static Node CreateNode(NodeMode mode)
        {
            Node node = new Node();
            node.Mode = mode;
            return node;
        }

        public static Node CreateNode(
            DateTime createdOn,
            NodeMode mode = NodeMode.MultipleVersions,
            string templateName = null,
            string name = null,
            long ID = 0,
            long? parentNodeID = 0,
            string languageLock = null
            )
        {
            return new Node()
            {
                ID = ID,
                Mode = mode,
                TemplateName = templateName,
                LanguageLock = languageLock,
                Name = name,
                CreatedOn = createdOn,
                ParentNodeID = parentNodeID
            };
        }

        #endregion

        #region properties

        public dynamic Bag
        {
            get
            {
                if (this._bag == null)
                {
                    return this._bag = new NodeBag(this);
                }
                return this._bag;
            }
        }

        public IEnumerable<Node> Nodes
        {
            get
            {
                if (this.StorageState == NodeStorageState.New)
                {
                    throw new ApplicationException("Node not yet created, could not have child nodes");
                }

                IEnumerable<Node> nodes = Context.Application.StorageCache.GetSubNodes(this.Path);
                if (nodes != null)
                {
                    return nodes;
                }
                if (this.ProviderRoot)
                {
                    nodes = this.ContentTreeProvider.GetRootNodes(this.ID).ToList();
                    foreach (Node node in nodes)
                    {
                        node.Path = this.Path.CombineUrl(node.Name);
                        if (node.ContentProviderTypeName == null)
                        {
                            node.ContentTreeProvider = this.ContentTreeProvider;
                            node.ContentProviderTypeName = this.ContentProviderTypeName;
                        }
                        Context.Application.StorageCache.SetNode(node);
                    }
                }
                else
                {
                    nodes = this.ContentTreeProvider.GetNodes(this);
                    foreach (Node node in nodes)
                    {

                        node.Path = this.Path.CombineUrl(node.Name);
                        node.ContentProviderTypeName = this.ContentProviderTypeName;
                        Context.Application.StorageCache.SetNode(node);
                    }
                }
                Context.Application.StorageCache.SetSubNodes(this.Path, nodes);
                return nodes;
            }
        }

        public string LanguageLock
        {
            get;
            internal set;
        }

        public Node Parent {
            get
            {
                string parentPath = this.GetParentPath(this.Path);
                if (string.IsNullOrEmpty(parentPath)) return null;
                
                Node parentNode = Context.Application.StorageCache.GetNode(parentPath);
                if (parentNode != null)
                {
                    return parentNode;
                }

                parentNode = Context.Application.Tree.GetNode(parentPath);

                return parentNode;
            }
        }

        public long? ParentNodeID
        {
            get;
            internal set;
        }

        public bool ReadOnly
        {
            get;
            internal set;
        }

        public NodeMode Mode
        {
            get;
            internal set;
        }

        internal IContentTreeProvider ContentTreeProvider
        {
            get;
            set;
        }

        internal string ContentProviderTypeName
        {
            get;
            set;
        }

        public string CurrentProviderPath
        {
            get;
            internal set;
        }

        public string Path
        {
            get;
            internal set;
        }

        public string TemplateName
        {
            get;
            set;
        }

        public IEnumerable<FieldMeta> Fields
        {
            get
            {
                TemplateMeta templateMeta = Context.Application.Templates.Where(p => p.Name == this.TemplateName).FirstOrDefault();
                if (templateMeta != null)
                {
                    return templateMeta.FieldMetasWithInherited;
                }
                throw new ApplicationException("Template not found");
            }
        }

        public long ID
        {
            get;
            internal set;
        }

        public string Name
        {
            get;
            internal set;
        }

        internal bool ProviderRoot
        {
            get;
            set;
        }

        internal List<BaseDataType> SharedFields
        {
            get;
            set;
        }

        public DateTime CreatedOn
        {
            get;
            internal set;
        }

        #endregion

        #region methods

        public void SelectVersion(string language = null, int? versionNumber = null)
        {
            if (this.Mode == NodeMode.SingleVersion)
            {
                throw new ApplicationException(string.Format("Node {0} does not support versioning", this.Path));
            }

            if (language == null)
            {
                language = Context.Request.Language.Name;
            }

            if (versionNumber == null)
            {
                versionNumber = this.GetDefaultVersionNumber(language);
            }

            Context.Request.SetSelectedVersion(this.Path, language, versionNumber.Value);
        }

        public void UnselectVersion()
        {
            if (this.Mode == NodeMode.SingleVersion)
            {
                throw new ApplicationException(string.Format("Node {0} does not support versioning", this.Path));
            }
            Context.Request.UnselectVersion(this.Path);
        }

        private bool ValidateField(string fieldName, Type type = null)
        {
            if (this.TemplateName != null)
            {
                TemplateMeta template = Context.Application.GetTemplate(this.TemplateName);
                if (template != null)
                {
                    return template.ValidateField(fieldName, type);
                }
                else
                {
                    throw new ApplicationException(string.Format("Template {0} does not exist", this.TemplateName));
                }
            }
            throw new ApplicationException("Node does not have template defined");
        }

        private bool IsFieldShared(string fieldName)
        {
            if (this.TemplateName != null)
            {
                TemplateMeta template = Context.Application.GetTemplate(this.TemplateName);
                if (template != null)
                {
                    return template.IsFieldShared(fieldName);
                }
            }
            throw new ApplicationException(string.Format("Template {0} not found", this.TemplateName));
        }

        private NodeVersion GetVersion(string language, int version)
        {
            NodeVersion nodeVersion = Context.Application.StorageCache.GetNodeVersion(this.Path, language, version);
            if (nodeVersion != null)
            {
                return nodeVersion;
            }
            nodeVersion = this.ContentTreeProvider.GetNodeVersion(this, language, version);
            
            if (nodeVersion != null) {
                Context.Application.StorageCache.SetNodeVersion(this.Path, nodeVersion);
            }
            return nodeVersion;
        }

        public Node GetChild(string name)
        {
            string nodePath = this.Path.CombineUrl(name);
            Node node = Context.Application.StorageCache.GetNode(nodePath);

            if (node != null)
            {
                return node;
            }

            if (this.ProviderRoot)
            {
                node = this.ContentTreeProvider.GetNode("", name, this.ID);
                if (node != null)
                {
                    node.CurrentProviderPath = name;
                }
            }
            else
            {
                node = this.ContentTreeProvider.GetNode(this.CurrentProviderPath, name, this.ID);
                if (node != null)
                {
                    node.CurrentProviderPath = this.CurrentProviderPath.CombineUrl(name);
                }
            }
            if (node != null)
            {
                node.Path = nodePath;
                if (node.ContentProviderTypeName == null)
                {
                    node.ContentTreeProvider = this.ContentTreeProvider;
                    node.ContentProviderTypeName = this.ContentProviderTypeName;
                }
            }
            return node;
        }

        public T GetValue<T>(string fieldName, int? versionNumber = null, string languageIdent = null, bool throwException = false) where T:BaseDataType
        {
            if (!this.ValidateField(fieldName, typeof(T)))
            {
                if (throwException)
                    throw new ApplicationException(string.Format("Field with name {0} does not exist or is different type than {1}", fieldName, typeof(T).Name));
                else return default(T);
            }
            
            if (this.Mode == NodeMode.SingleVersion)
            {
                if (this.SharedFields != null)
                {
                    return this.SharedFields.Where(p => p.Name == fieldName).FirstOrDefault() as T;
                }
                return null;
            }
            else
            {
                if (this.IsFieldShared(fieldName))
                {
                    
                }
                else
                {
                    if (languageIdent == null)
                    {
                        languageIdent = Context.Request.Language.Name;
                    }

                    if (versionNumber == null)
                    {
                        versionNumber = this.GetDefaultVersionNumber(languageIdent);
                    }

                    NodeVersion version = this.GetVersion(languageIdent, versionNumber.Value);

                    if (version != null)
                    {
                        return version.GetValue<T>(fieldName);
                    }
                }
                return null;
            }
        }

        // /Content/ComputerShop/About
        private string GetParentPath(string childPath)
        {
            int lastSlashPosition = childPath.LastIndexOf('/');
            if (lastSlashPosition > 0) {
                return childPath.Substring(0, lastSlashPosition);
            }
            return null;
        }

        private int GetDefaultVersionNumber(string language)
        {
            if (Context.Request.CurrentSite != null)
            {
                if (Context.Request.CurrentSite.DefaultNodeVersion == SiteDefaultNodeVersion.Published)
                {
                    var versionNumber = (from p in this.VersionsMeta
                                         where p.LanguageIdent == language && p.State == NodeVersionState.Published
                                         select p.VersionNumber).FirstOrDefault();
                    return versionNumber;
                }
                else
                {
                    var versionNumber = (from p in this.VersionsMeta
                                         where p.LanguageIdent == language && p.State == NodeVersionState.Draft
                                         select p.VersionNumber).FirstOrDefault();
                    return versionNumber;
                }
            }
            else
            {
                return 1;
            }
        }

        #endregion

        #region editing

        internal NodeStorageState StorageState
        {
            get;
            set;
        }

        internal void AddNewVersion(NodeVersion nodeVersion)
        {
            nodeVersion.VersionNumber = 1;
            string nodeVersionKey = nodeVersion.LanguageName + "_" + nodeVersion.VersionNumber.ToString();
            //this.Versions.Add(nodeVersionKey, nodeVersion);
        }

        public Node CreateNode(string name, string templateName, string newVersionLanguageName = null)
        {
            Node newNode = new Node()
            {
                TemplateName =              templateName,
                Path =                      this.Path.CombineUrl(name),
                Name =                      name,
                ContentProviderTypeName =   this.ContentProviderTypeName,
                ContentTreeProvider =       this.ContentTreeProvider,
                CurrentProviderPath =       this.CurrentProviderPath.CombineUrl(name),
                StorageState =              NodeStorageState.New
            };

            if (Context.Request.CurrentStorageEditor != null)
            {
                if (newVersionLanguageName == null) {
                    newVersionLanguageName = Context.Request.Language.Name;
                }
                NodeVersion newNodeVersion = new NodeVersion(newVersionLanguageName);
                newNode.AddNewVersion(newNodeVersion);
                Context.Request.CurrentStorageEditor.AddNewNode(newNode);
            }
            else
            {
                throw new ApplicationException("StorageEditor not created or calling CreateNode from outside of using statement.");
            }
            return newNode;
        }

        public int CreateNodeVersion(string languageName = null)
        {
            return 0;
        }

        public void SetValue(string fieldName, BaseDataType value, string languageName = null, int? versionNumber = null)
        {
            if (this.ReadOnly) throw new ApplicationException(string.Format("Node {0} is readonly", this.Path));

            if (this.Mode == NodeMode.SingleVersion)
            {
                if (this.SharedFields == null) {
                    this.SharedFields = new List<BaseDataType>();
                }
                this.SharedFields.RemoveAll(p => p.Name == fieldName);
                value.Name = fieldName;
                this.SharedFields.Add(value);
            }
            else
            {
                //if (this.Versions.ContainsKey(key))
                //{
                //    NodeVersion currentNodeVersion = this.Versions[key];
                //    value.Name = fieldName;
                //    currentNodeVersion.SetValue<T>(fieldName, value);
                //}
            }
        }

        #endregion

        #region BaseModelObject support methods

        public T As<T>() where T: BaseModelObject
        {
            foreach (var fieldMeta in this.Fields)
            {
                
            }

            return null;
        }

        #endregion

    }

}